package com.github.limit.handler;

import com.github.limit.annotation.Limit;
import com.github.limit.entity.CurrentLimit;
import com.github.limit.persistence.IAccessLimit;
import com.github.limit.util.DateUtils;
import com.github.limit.util.LimitUtils;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author: wangdejun
 * @create: 2019-10-11 17:21
 **/
public abstract class AbstractLimitHandler<T extends Annotation> implements LimitHandler<T> {

    private static final Map<Class<? extends Annotation>, LimitHandler> limitHandlerMap = new ConcurrentHashMap<>(8);

    public static final String LIMIT = "limit";

    public static final String LIMIT_ACCESS_KEY_PREFIX = LIMIT + ":access:";

    public static final String LIMIT_DENY_KEY_PREFIX = LIMIT + ":deny:";

    private static final String MI_SECOND_TIME = "yyyyMMddHHmmssSSS";

    private static final String SECOND_TIME = "yyyyMMddHHmmss";

    private static final String MINUTE_TIME = "yyyyMMddHHmm";

    private static final String HOUR_TIME = "yyyyMMddHH";

    private static final String DAY_TIME = "yyyyMMdd";


    @Autowired
    protected IAccessLimit iAccessLimit;

    @PostConstruct
    private void init() {
        // 校验注解是否包含必填参数
        Class<?> clazz = getType();
        if (!clazz.isAnnotationPresent(Limit.class)) {
            throw new RuntimeException("自定义注解必须用com.smcx.securitydemo.limit.annotation.Limit标注");
        }
        try {
            Method limit = clazz.getDeclaredMethod("limit");
//            Field limit = clazz.getDeclaredField("limit");
            if (limit.getReturnType() != long.class) {
                throw new RuntimeException("自定义注解limit属性必须为long类型");
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            throw new RuntimeException("自定义注解必须包含limit属性");
        }

        try {
            Method timeUnit = clazz.getDeclaredMethod("timeUnit");
            if (timeUnit.getReturnType() != TimeUnit.class) {
                throw new RuntimeException("自定义注解timeUnit属性必须为TimeUnit类型");
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("自定义注解必须包含timeUnit属性");
        }

        try {
            Method order = clazz.getDeclaredMethod("order");
            if (order.getReturnType() != int.class) {
                throw new RuntimeException("自定义注解order属性必须为int类型");
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException("自定义注解必须包含order属性");
        }

        limitHandlerMap.put(getType(), this);
    }


    public static final LimitHandler getHandler(Annotation annotation) {
        return limitHandlerMap.get(annotation.annotationType());
    }

    /**
     * 责任链处理接口
     *
     * @param t
     * @param method
     * @return
     */
    @Override
    public final boolean handler(T t, Method method) {
        // 通用校验
        String methodName = method.getName();
        String className = method.getDeclaringClass().getSimpleName(); // 减少可长度，采用simpleName，对于重名bean，会存在问题
        String[] accessKey = getAccessKey(t, className, methodName);
        long count = iAccessLimit.getCount(accessKey[0], accessKey[1]);
        if (count >= getLimit(t)) {
            String[] denKey = getDenKey(t, className, methodName);
            iAccessLimit.increase(denKey[0], denKey[1]);
            return false;
        }
        return doHandler(t, className, methodName);
    }

    /**
     * 实际的限流处理逻辑
     *
     * @param t
     * @param methodName
     * @return
     */
    public abstract boolean doHandler(T t, String className, String methodName);

    /**
     * 限流处理类型的key
     *
     * @return
     */
    public abstract Class<? extends Annotation> getType();


    /**
     * 获取限流通过的key
     *
     * @param t
     * @param methodName
     * @return
     */
    public final String[] getAccessKey(T t, String className, String methodName) {
        String accessKey = baseKey(timeStr(t))
                + ":" + accessKey(t, className, methodName);
        return new String[]{LimitUtils.replaceAndSbuLast(LIMIT_ACCESS_KEY_PREFIX + ":" + getName()),
                accessKey};
    }

    public abstract String accessKey(T t, String className, String methodName);


    /**
     * 获取限流拒绝的key
     *
     * @param t
     * @param methodName
     * @return
     */
    public final String[] getDenKey(T t, String className, String methodName) {
        String denyKey = baseKey(timeStr(t)) + ":" + denyKey(t, className, methodName);
        denyKey = denyKey.replaceAll(":{2,}", ":");
        return new String[]{LIMIT_DENY_KEY_PREFIX + ":" + getName(), denyKey};
    }

    public abstract String denyKey(T t, String className, String methodName);


    /**
     * 获取限流数值
     *
     * @param t
     * @return
     */
    public abstract long getLimit(T t);

    public final String timeStr(T t) {
        TimeUnit timeUnit = LimitUtils.getAnnotationValue(t, "timeUnit");
        return Optional.ofNullable(timeUnit).map(this::timeStr).orElse(timeStr(TimeUnit.SECONDS));
    }


    /**
     * 将timeUnit转换成时间字符串
     *
     * @param timeUnit
     * @return
     */
    public final String timeStr(TimeUnit timeUnit) {
        Date date = new Date();
        String timeStr = "";
        switch (timeUnit) {
            case MILLISECONDS:
                timeStr = DateUtils.formatDate(date, MI_SECOND_TIME);
                break;
            case SECONDS:
                timeStr = DateUtils.formatDate(date, SECOND_TIME);
                break;
            case MINUTES:
                timeStr = DateUtils.formatDate(date, MINUTE_TIME);
                break;
            case HOURS:
                timeStr = DateUtils.formatDate(date, HOUR_TIME);
                break;
            case DAYS:
                timeStr = DateUtils.formatDate(date, DAY_TIME);
                break;
            default:
                timeStr = DateUtils.formatDate(date, SECOND_TIME);
                break;
        }
        return timeStr;
    }

    private String baseKey(String timeStr) {
        return timeStr + ":";
    }

    public static final List<? extends CurrentLimit> limitDataList() {
        List<? extends CurrentLimit> limitDataList = new ArrayList<>();
        limitHandlerMap.keySet().stream().forEach(key -> {
            List list = limitHandlerMap.get(key).limitList();
            limitDataList.addAll(list);
        });
        return limitDataList;
    }

    public abstract String getName();

}
